<?php
declare (strict_types = 1);

namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\helper\Str;
use think\facade\Db;
use think\facade\Config;

class ModelGener extends Command
{
    const FILE_DIR = 'model';

    const NS_PREFIX = 'app\\' . self::FILE_DIR;

    protected function configure()
    {
        // 指令配置
        $this->setName('gen_model')
            ->setDescription('the gen_model command');
    }

    protected function execute(Input $input, Output $output)
    {
        // 指令输出
        $output->writeln('gen_model ....');

        $tables = Db::getTables();

        foreach ($tables as $table) {
            // 这里获取到的是原始表名，有db配置的前缀的；
            $fields = Db::getFields($table);

            $this->genFile($table, $fields);
        }
    }

    public function genFile($table, $fields)
    {
        // 先分析实际表名
        $actualTableName = $table;
        $prefix = Config::get('database.connections.' . Config::get('database.default') . '.prefix');
        if (!empty($prefix) && Str::startsWith($table, $prefix) !== false)
        {
            $actualTableName = Str::substr($table, strlen($prefix));
        }

        // 构造类名
        $className = Str::studly($actualTableName);

        // 写入文件
        // 创建目录
        $dir = $this->getApp()->getAppPath() . self::FILE_DIR;
        if (!is_dir($dir)) {
            mkdir($dir, 0755);
        }
        $dir = $this->getApp()->getAppPath() . self::FILE_DIR . DIRECTORY_SEPARATOR . 'base';
        if (!is_dir($dir)) {
            mkdir($dir, 0755);
        }
        // 写入文件 base model
        $baseClassName = 'Base' . $className . 'Model';
        $file = $dir . DIRECTORY_SEPARATOR . $baseClassName . '.php';
        if (file_exists($file)) {
            $file = $dir . DIRECTORY_SEPARATOR . $baseClassName . time() . '.php';
        }
        // 构造文件内容
        $code = $this->renderBaseFileByTpl($table, $baseClassName, $fields);
        file_put_contents($file, $code);

        // 写入文件 model
        // 构造文件内容
        $code = $this->renderFileByTpl($table, $className, $baseClassName, $fields);
        $dir = $this->getApp()->getAppPath() . self::FILE_DIR;
        $file = $dir . DIRECTORY_SEPARATOR . $className . '.php';
        if (file_exists($file)) {
            $file = $dir . DIRECTORY_SEPARATOR . $className . time() . '.php';
        }
        file_put_contents($file, $code);
    }

    public function renderBaseFileByTpl($table, $className, $fields)
    {
        $file = '<?php' . "\n\n";
        $file .= "namespace " . self::NS_PREFIX . "\\base;\n\n";
        $file .= '' . "\n";
        $file .= '/**' . "\n";
        $file .= ' * ' . $className . ' base model' . "\n";
        // 构造字段属性
        foreach ($fields as $field) {
            $fieldExtra = $this->castToPhpType($field);
            $file .= ' * @property ' . $fieldExtra['phpType'] . ' $' . $field['name'] . ' ' . $fieldExtra['label'] . "\n";
        }

        $file .= ' */' . "\n";
        $file .= 'class ' . $className . " extends \\think\\Model\n{\n\n";
        $file .= '    protected $schema = [' . "\n";
        foreach ($fields as $field) {
            $fieldExtra = $this->castToPhpType($field);
            $file .= "    '{$field['name']}' => '{$fieldExtra['phpType']}',\n";
        }
        $file .= '    ];';
        $file .= "\n}\n";

        return $file;
    }

    public function renderFileByTpl($table, $className, $baseClassName, $fields)
    {
        $file = '<?php' . "\n\n";
        $file .= "namespace " . self::NS_PREFIX . ";\n\n";
        $file .= "use " . self::NS_PREFIX . "\\base\\{$baseClassName};\n\n";
        $file .= '/**' . "\n";
        $file .= ' * ' . $className . ' model' . "\n";
        $file .= ' */' . "\n";
        $file .= 'class ' . $className . " extends {$baseClassName}\n{\n";

        $file .= "\n}\n";

        return $file;
    }

    public function castToPhpType($rawField)
    {
        if (Str::endsWith($rawField['type'], ' unsigned')) {
            $rawField['type'] = Str::substr($rawField['type'], 0, strlen($rawField['type']) - strlen(' unsigned'));
        }
        $firstPOS = strpos($rawField['type'], '(');
        $type     = substr($rawField['type'], 0, $firstPOS > 0 ? $firstPOS : strlen($rawField['type']));
//        $type     = str_replace(array('big', 'small', 'medium', 'tiny'), '', $type);

        $field = [];
        $field['name'] = $rawField['name'];
        $field['label'] = !empty($rawField['comment']) ? $rawField['comment'] : Str::snake($rawField['name']);
        $field['label'] = Str::title(str_replace('_', ' ', $field['label']));
        $field['notnull'] = $rawField['notnull'];

        if($type == 'enum' or $type == 'set')
        {
            $rangeBegin  = $firstPOS + 2;                       // Remove the first quote.
            $rangeEnd    = strrpos($rawField['type'], ')') - 1;   // Remove the last quote.
            $range       = substr($rawField['type'], $rangeBegin, $rangeEnd - $rangeBegin);
            $field['type'] = $rawField['type'];
            $field['phpType'] = 'string';
            $field['options']['enum']  = '\'' .str_replace("','", ',', $range) . '\'';
        }
        elseif($type == 'varchar')
        {
            $begin  = $firstPOS + 1;
            $end    = strpos($rawField['type'], ')', $begin);
            $length = substr($rawField['type'], $begin, $end - $begin);
            $field['type']   = 'varchar';
            $field['phpType'] = 'string';
            $field['options']['max'] = $length;
            $field['options']['min'] = 0;
        }
        elseif($type == 'char')
        {
            $begin  = $firstPOS + 1;
            $end    = strpos($rawField['type'], ')', $begin);
            $length = substr($rawField['type'], $begin, $end - $begin);
            $field['type']   = 'char';
            $field['phpType'] = 'string';
            $field['options']['max'] = $length;
            $field['options']['min'] = 0;
        }
        elseif($type == 'int')
        {
            $begin  = $firstPOS + 1;
            $end    = strpos($rawField['type'], ')', $begin);
            $length = substr($rawField['type'], $begin, $end - $begin);
            $field['type'] = 'int';
            $field['phpType'] = 'int';
            $field['options']['max'] = $length;
            $field['options']['min'] = 0;
        }
        elseif($type == 'mediumint' || $type == 'bigint' || $type == 'tinyint' || $type == 'smallint')
        {
            $length = 1;
            switch ($type) {
                case 'bigint':
                    $length = 10;
                    break;
                case 'mediumint':
                    $length = 8;
                    break;
                case 'tinyint':
                    $length = 1;
                    break;
                case 'smallint':
                    $length = 4;
                    break;
            }
            $field['type'] = $type;
            $field['phpType'] = 'int';
            $field['options']['max'] = $length;
            $field['options']['min'] = 0;
        }
        elseif($type == 'float' or $type == 'double')
        {
            $field['type'] = 'float';
            $field['phpType'] = 'float';
        }
        elseif($type == 'date' || $type == 'datetime')
        {
            $field['type'] = $type;
            $field['phpType'] = 'string';
        }
        elseif($type == 'text' || $type == 'mediumtext')
        {
            $field['type'] = $type;
            $field['phpType'] = 'string';
        }
        else
        {
            $field['type'] = $type;
            $field['phpType'] = 'mixed';
        }

        return $field;
    }
}
